/**
 ***************************************************************************************************
 *
 * @file ble_tys_task.c
 *
 * @brief Tuya service transport task implementation.
 *
 * Copyright (C) Eker 2021
 *
 *
 ***************************************************************************************************
 */

/**
 ***************************************************************************************************
 * @addtogroup TYSTASK
 * @{
 ***************************************************************************************************
 */

/*
 * INCLUDE FILES
 ***************************************************************************************************
 */
#include "rwip_config.h"

#include "gap.h"
#include "gattc_task.h"
#include "attm.h"
#include "prf_utils.h"

#include "ke_mem.h"
#include "co_utils.h"
#include "co.h"
#include "ble_tys.h"
#include "ble_tys_task.h"

#define APP_LOG_DOMAIN      "tys"
#define APP_LOG_LEVEL       APP_LOG_LEVEL_TYS
#include "app_log.h"

/*
 * LOCAL FUNCTIONS DEFINITIONS
 ***************************************************************************************************
 */
static void ty_server_send_error(uint16_t operation, uint8_t status,
                                 ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
    struct ty_server_error_evt *evt;
    evt = KE_MSG_ALLOC(TY_SERVER_ERROR, src_id, dest_id, ty_server_error_evt);
    evt->operation = operation;
    evt->status = status;
    ke_msg_send(evt);
}

/**
 ***************************************************************************************************
 * @brief Handles reception of the @ref TY_SERVER_ENABLE_REQ message.
 * The handler enables the 'Profile' Server Role.
 * @param[in] msgid Id of the message received (probably unused).
 * @param[in] param Pointer to the parameters of the message.
 * @param[in] dest_id ID of the receiving task instance (probably unused).
 * @param[in] src_id ID of the sending task instance.
 * @return If the message was consumed or not.
 ***************************************************************************************************
 */
static int ty_server_enable_req_handler(ke_msg_id_t const msgid,
                                        struct ty_server_enable_req const *param,
                                        ke_task_id_t const dest_id,
                                        ke_task_id_t const src_id)
{
    LOG_VBS_FUNC();
    uint8_t state = ke_state_get(dest_id);

    // check state of the task
    if(state == TYS_IDLE)
    {
        struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);

        // check provided values
        if((param->conidx > BLE_CONNECTION_MAX)
            || (gapc_get_conhdl(param->conidx) == GAP_INVALID_CONHDL))
        {
            // an error occurs, trigg it.
            struct ty_server_enable_rsp* rsp = KE_MSG_ALLOC(TY_SERVER_ENABLE_RSP, src_id,
                                                            dest_id, ty_server_enable_rsp);
            rsp->conidx = param->conidx;
            rsp->status = (param->conidx > BLE_CONNECTION_MAX)
                            ? GAP_ERR_INVALID_PARAM : PRF_ERR_REQ_DISALLOWED;
            ke_msg_send(rsp);
        }
        else
        {
            ty_server_env->ntf_cfg[param->conidx] = param->ntf_cfg;
        }
    }

    return KE_MSG_CONSUMED;
}

/**
 ***************************************************************************************************
 * @brief Handles reception of the attribute info request message.
 *
 * @param[in] msgid Id of the message received (probably unused).
 * @param[in] param Pointer to the parameters of the message.
 * @param[in] dest_id ID of the receiving task instance (probably unused).
 * @param[in] src_id ID of the sending task instance.
 * @return If the message was consumed or not.
 ***************************************************************************************************
 */
static int ty_server_gattc_att_info_req_ind_handler(ke_msg_id_t const msgid,
                                                    struct gattc_att_info_req_ind *param,
                                                    ke_task_id_t const dest_id,
                                                    ke_task_id_t const src_id)
{
    LOG_VBS_FUNC();
    struct gattc_att_info_cfm * cfm;
    uint8_t att_idx = 0;
    // retrieve handle information
    uint8_t status = ty_server_get_att_idx(param->handle, &att_idx);

    //Send write response
    cfm = KE_MSG_ALLOC(GATTC_ATT_INFO_CFM, src_id, dest_id, gattc_att_info_cfm);
    cfm->handle = param->handle;
    if(status == GAP_ERR_NO_ERROR)
    {
        // check if it's a client configuration char
        if(att_idx == TYS_IDX_NTF_CFG)
        {
            // CCC attribute length = 2
            cfm->length = sizeof(uint16_t);
        }
        // not expected request
        else
        {
            cfm->length = 0;
            status = ATT_ERR_WRITE_NOT_PERMITTED;
        }
    }
    cfm->status = status;
    ke_msg_send(cfm);

    return (KE_MSG_CONSUMED);
}

/**
 ***************************************************************************************************
 * @brief Handles reception of the @ref GATTC_WRITE_REQ_IND message.
 * @param[in] msgid Id of the message received (probably unused).
 * @param[in] param Pointer to the parameters of the message.
 * @param[in] dest_id ID of the receiving task instance (probably unused).
 * @param[in] src_id ID of the sending task instance.
 * @return If the message was consumed or not.
 ***************************************************************************************************
 */
static int ty_server_gattc_write_req_ind_handler(ke_msg_id_t const msgid,
                                                 struct gattc_write_req_ind const *param,
                                                 ke_task_id_t const dest_id,
                                                 ke_task_id_t const src_id)
{
    LOG_VBS_FUNC();
    struct gattc_write_cfm * cfm;
    uint8_t att_idx = 0;
    uint8_t conidx = KE_IDX_GET(src_id);
    // retrieve handle information
    uint8_t status = ty_server_get_att_idx(param->handle, &att_idx);

    // if the attribute has been found, status is GAP_ERR_NO_ERROR
    if (status == GAP_ERR_NO_ERROR)
    {
        struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);

        // only update configuration if value for stop or notification enable
        if (att_idx == TYS_IDX_NTF_CFG)
        {
            // extract value before check
            uint16_t ntf_cfg = co_read16p(&param->value[0]);
            if((ntf_cfg == PRF_CLI_STOP_NTFIND) || (ntf_cfg == PRF_CLI_START_NTF)
                || (ntf_cfg == PRF_CLI_START_IND))
            {
                // conserve information in environment
                ty_server_env->ntf_cfg[conidx] = ntf_cfg;
            }
            // inform APP of configuration change
            struct ty_server_ntf_cfg_ind * ind =
                KE_MSG_ALLOC(TY_SERVER_NTF_CFG_IND,
                             prf_dst_task_get(&(ty_server_env->prf_env), conidx),
                             KE_BUILD_ID(dest_id, conidx),
                             ty_server_ntf_cfg_ind);
            ind->conidx = conidx;
            ind->ntf_cfg = ty_server_env->ntf_cfg[conidx];
            ke_msg_send(ind);
        }
        else if (att_idx == TYS_IDX_WRITE_VAL || att_idx == TYS_IDX_READ_VAL)
        {
            // received data from peer device, send a indication to APP
            struct ty_server_write_ind * ind =
                KE_MSG_ALLOC_DYN(TY_SERVER_WRITE_IND,
                                 prf_dst_task_get(&(ty_server_env->prf_env), conidx),
                                 dest_id,
                                 ty_server_write_ind,
                                 param->length);
            ind->conidx = conidx;
            ind->handle = param->handle;
            ind->offset = param->offset;
            ind->length = param->length;
            memcpy(ind->value, param->value,param->length);
            ke_msg_send(ind);
        }
        else
        {
            status = PRF_APP_ERROR;
        }

    }

    //Send write response
    cfm = KE_MSG_ALLOC(GATTC_WRITE_CFM, src_id, dest_id, gattc_write_cfm);
    cfm->handle = param->handle;
    cfm->status = status;
    ke_msg_send(cfm);

    return (KE_MSG_CONSUMED);
}

/**
 ***************************************************************************************************
 * @brief Handles reception of the @ref GATTC_READ_REQ_IND message.
 * @param[in] msgid Id of the message received (probably unused).
 * @param[in] param Pointer to the parameters of the message.
 * @param[in] dest_id ID of the receiving task instance (probably unused).
 * @param[in] src_id ID of the sending task instance.
 * @return If the message was consumed or not.
 ***************************************************************************************************
 */
static int ty_server_gattc_read_req_ind_handler(ke_msg_id_t const msgid,
                                                struct gattc_read_req_ind const *param,
                                                ke_task_id_t const dest_id,
                                                ke_task_id_t const src_id)
{
    LOG_VBS_FUNC();
    struct gattc_read_cfm * cfm;
    struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);
    uint8_t att_idx = 0;
    uint8_t conidx = KE_IDX_GET(src_id);
    // retrieve handle information
    uint8_t status = ty_server_get_att_idx(param->handle, &att_idx);
    uint16_t length = 0;

    // If the attribute has been found, status is GAP_ERR_NO_ERROR
    if (status == GAP_ERR_NO_ERROR)
    {
        // read notification information
        if (att_idx == TYS_IDX_NTF_CFG)
        {
            length = sizeof(uint16_t);
        }
        else
        {
            status = PRF_APP_ERROR;
        }
    }

    //Send write response
    cfm = KE_MSG_ALLOC_DYN(GATTC_READ_CFM, src_id, dest_id, gattc_read_cfm, length);
    cfm->handle = param->handle;
    cfm->status = status;
    cfm->length = length;
    if (status == GAP_ERR_NO_ERROR)
    {
        // read notification information
        if (att_idx == TYS_IDX_NTF_CFG)
        {
            cfm->value[0] = ty_server_env->ntf_cfg[conidx]; //characteristic value
        }
    }
    ke_msg_send(cfm);

    return (KE_MSG_CONSUMED);
}

static int ty_server_send_data_to_gattc(ke_task_id_t const dest_id, uint8_t conidx)
{
    LOG_VBS_FUNC();
    struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);
    uint16_t att_handle = ty_server_get_att_handle(TYS_IDX_NTF_VAL);
    uint8_t pdata[256];
    uint16_t length = 0;

    // get the size of data to be sent
    co_fifo_out(&(ty_server_env->send_fifo[conidx]), (uint8_t*)&length, sizeof(uint16_t));

    // get the data to be sent
    uint16_t len = co_fifo_out(&(ty_server_env->send_fifo[conidx]), pdata, length);

    LOG_DBG("length: %d, out_len: %d", length, len);
    if (len == length)
    {
        struct gattc_send_evt_cmd *req = KE_MSG_ALLOC_DYN(GATTC_SEND_EVT_CMD,
                                                          KE_BUILD_ID(TASK_GATTC, conidx), 
                                                          prf_get_task_from_id(KE_BUILD_ID(TASK_ID_TYS, conidx)), 
                                                          gattc_send_evt_cmd,
                                                          len);
        if(ty_server_env->ntf_cfg[conidx] == PRF_CLI_START_NTF)
        {
            req->operation = GATTC_NOTIFY;
        }
        else
        {
            req->operation = GATTC_INDICATE;
        }
        req->handle = att_handle;
        req->length = len;
        memcpy(req->value, pdata, req->length);
        ke_msg_send(req);

        ke_state_set(dest_id, TYS_BUSY);
    }
    else
    {
        LOG_ERR("The number of data is not expected!!");
        ke_state_set(dest_id, TYS_CONNECTED);
    }

    return (KE_MSG_CONSUMED);

}

/**
 ***************************************************************************************************
 * @brief Handles @ref GATTC_CMP_EVT for GATTC_NOTIFY message meaning that Measurement
 * notification has been correctly sent to peer device (but not confirmed by peer device).
 * *
 * @param[in] msgid     Id of the message received.
 * @param[in] param     Pointer to the parameters of the message.
 * @param[in] dest_id   ID of the receiving task instance
 * @param[in] src_id    ID of the sending task instance.
 * @return If the message was consumed or not.
 ***************************************************************************************************
 */
static int ty_server_gattc_cmp_evt_handler(ke_msg_id_t const msgid,
                                           struct gattc_cmp_evt const *param,
                                           ke_task_id_t const dest_id,
                                           ke_task_id_t const src_id)
{
    LOG_VBS_FUNC();
    uint8_t conidx = KE_IDX_GET(src_id);
    struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);

    struct ty_server_buffer_empty_evt *evt;
    uint8_t state = ke_state_get(dest_id);
    if (state == TYS_IDLE || state > TYS_BUSY)
    {
        return (KE_MSG_CONSUMED);
    }
    if(!(ty_server_env->ntf_cfg[conidx] == PRF_CLI_START_NTF
        || ty_server_env->ntf_cfg[conidx] == PRF_CLI_START_IND))
    {
        return (KE_MSG_CONSUMED);
    }

    if (co_fifo_is_empty(&(ty_server_env->send_fifo[conidx])))
    {
        ke_state_set(dest_id, TYS_CONNECTED);

        // send fifo empty
        evt = KE_MSG_ALLOC(TY_SERVER_BUFFER_EMPTY,
                           prf_dst_task_get(&(ty_server_env->prf_env), conidx),
                           dest_id,
                           ty_server_buffer_empty_evt);
        evt->operation = param->operation;
        evt->status = param->status;
        ke_msg_send(evt);

        return (KE_MSG_CONSUMED);
    }

    switch (param->operation)
    {
        case (GATTC_NOTIFY):
            {
                ty_server_send_data_to_gattc(dest_id, conidx);
            } 
            break;

        default:
            ke_state_set(dest_id, TYS_CONNECTED);
            break;
    }
    
    return (KE_MSG_CONSUMED);
}

static int ty_server_send_ntf_cmd_handler(ke_msg_id_t const msgid,
                                          struct ty_server_send_ntf_cmd const *param,
                                          ke_task_id_t const dest_id,
                                          ke_task_id_t const src_id)
{
    LOG_VBS_FUNC();
    uint8_t conidx = KE_IDX_GET(src_id);
    uint8_t state = ke_state_get(dest_id);
    struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);

    if (state == TYS_IDLE || state > TYS_BUSY)
    {
        ty_server_send_error(TY_SERVER_SEND_NTF_CMD, (uint8_t)TY_SERVER_ERROR_NO_CONN, dest_id, src_id);
        return (KE_MSG_CONSUMED);
    }

    if(!(ty_server_env->ntf_cfg[conidx] == PRF_CLI_START_NTF
        || ty_server_env->ntf_cfg[conidx] == PRF_CLI_START_IND))
    {
        ty_server_send_error(TY_SERVER_SEND_NTF_CMD, (uint8_t)TY_SERVER_ERROR_NO_NOTI, dest_id, src_id);
        return (KE_MSG_CONSUMED);
    }
    if(co_fifo_is_full(&(ty_server_env->send_fifo[conidx]))
      || co_fifo_avail(&(ty_server_env->send_fifo[conidx])) < (param->length+2))
    {
        ty_server_send_error(TY_SERVER_SEND_NTF_CMD, (uint8_t)TY_SERVER_ERROR_NO_BUFF, dest_id, src_id);

        // send app full message
        struct ty_server_buffer_full_evt * evt =
            KE_MSG_ALLOC(TY_SERVER_BUFFER_FULL,
                         prf_dst_task_get(&(ty_server_env->prf_env), conidx),
                         dest_id,
                         ty_server_buffer_full_evt);
        evt->operation = (uint8_t)TY_SERVER_SEND_NTF_CMD;
        evt->status = (uint8_t)GAP_ERR_INSUFF_RESOURCES;
        ke_msg_send(evt);

        return (KE_MSG_CONSUMED);
    }
    co_fifo_in(&(ty_server_env->send_fifo[conidx]), (uint8_t*)&param->length, sizeof(uint16_t));
    co_fifo_in(&(ty_server_env->send_fifo[conidx]), (uint8_t*)param->value, param->length);

    if (state == TYS_BUSY)
    {
        return (KE_MSG_CONSUMED);
    }

    return ty_server_send_data_to_gattc(dest_id, conidx);
}

static int ty_server_gattc_mtu_changed_ind_handler(ke_msg_id_t const msgid,
                                                   struct gattc_mtu_changed_ind *param,
                                                   ke_task_id_t const dest_id,
                                                   ke_task_id_t const src_id)
{
    LOG_DBG("Tuya service MTU: %d", param->mtu);
    uint8_t conidx = KE_IDX_GET(src_id);
    struct tys_env_tag* ty_server_env = PRF_ENV_GET(TYS, tys);
    ty_server_env->mtu[conidx] = param->mtu;
    return KE_MSG_CONSUMED;
}

static int ty_server_default_handler(ke_msg_id_t const msgid,
                                     void const *param,
                                     ke_task_id_t const dest_id,
                                     ke_task_id_t const src_id)
{
    return (KE_MSG_CONSUMED);
}

/*
 * GLOBAL VARIABLE DEFINITIONS
 ***************************************************************************************************
 */
// Default state handlers definition
KE_MSG_HANDLER_TAB(tys)
{
    {KE_MSG_DEFAULT_HANDLER,        (ke_msg_func_t) ty_server_default_handler},
    {TY_SERVER_ENABLE_REQ,          (ke_msg_func_t) ty_server_enable_req_handler},
    {GATTC_ATT_INFO_REQ_IND,        (ke_msg_func_t) ty_server_gattc_att_info_req_ind_handler},
    {TY_SERVER_SEND_NTF_CMD,        (ke_msg_func_t) ty_server_send_ntf_cmd_handler},
    {GATTC_WRITE_REQ_IND,           (ke_msg_func_t) ty_server_gattc_write_req_ind_handler},
    {GATTC_READ_REQ_IND,            (ke_msg_func_t) ty_server_gattc_read_req_ind_handler},
    {GATTC_CMP_EVT,                 (ke_msg_func_t) ty_server_gattc_cmp_evt_handler},
    {GATTC_MTU_CHANGED_IND,         (ke_msg_func_t) ty_server_gattc_mtu_changed_ind_handler},
};

/*
 * GLOBAL FUNCTIONS DEFINITIONS
 ***************************************************************************************************
 */
void ty_server_task_init(struct ke_task_desc *task_desc)
{
    struct tys_env_tag *tys_env = PRF_ENV_GET(TYS, tys);

    task_desc->msg_handler_tab = tys_msg_handler_tab;
    task_desc->msg_cnt         = ARRAY_LEN(tys_msg_handler_tab);
    task_desc->state           = tys_env->state;
    task_desc->idx_max         = TYS_IDX_MAX;
}

uint8_t ty_server_check_fifo_full(uint8_t conidx)
{
    struct tys_env_tag *tys_env = PRF_ENV_GET(TYS, tys);
    return co_fifo_is_full(&(tys_env->send_fifo[conidx]));
}

uint32_t ty_server_get_free_size(uint8_t conidx)
{
    struct tys_env_tag *tys_env = PRF_ENV_GET(TYS, tys);
    return co_fifo_avail(&(tys_env->send_fifo[conidx]));
}
